%{
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>
#include "compile.h"
#include "operations.h"
#include "output.h"

void yyerror(char* s);
// called at end of parsed file
int yywrap();

void finBloc();

// from lexer
extern FILE* yyin;

const char* gInputFileName = NULL;

/*** Contexte transmis lors de la descente ***/

int gDeclConst; // vrai si on est en train de déclarer des variables 'const'
int gDeclGlobal = 1;
TypedAddr gDeclAddr;
char* gDeclName;

// Consomme expression. A appeler dans chaque reègle consommant une expression,
// pour assurer une libération correcte de la mémoire temporaire.
void CE(TypedAddr a)
{
	if (tabSymboles_isTmp(a.addr))
		tabSymboles_popTmp();
	else
		chk_init(a.addr);
}

%}

// 'name' alloué par lex, libéré ici.

%union { int val; Addr addr; char* name; Type type; 
		   TypedAddr typedAddr; ListeArguments listeArguments;} 

%token <val> tNUMBER 
%token <name> tID_VARIABLE

%token tCONSTANT tINT tVOID tWHILE tFOR tIF tELSE tPRINTF tRETURN tBREAK 
%token tCONTINUE

%token tGE tLE tNOT_EQUAL tEQUAL tPLUSEGAL tMOINSEGAL tMULEGAL tDIVEGAL tMODEGAL
%token tPLUSPLUS tMOINSMOINS

%type <type> Type Indirections
%type <addr> If_condition
%type <typedAddr> Expressions Expression
%type <val> For_Cond For_Converg Prototype
%type <listeArguments> ListeArguments OptListeArguments

// symbole de début
%start Programme

// donne également les priorités (par ordre croissant)
%right '=' tPLUSEGAL tMOINSEGAL tMULEGAL tDIVEGAL tMODEGAL
%left tEQUAL tNOT_EQUAL
%left '<' '>' tGE tLE
%left '+' '-'
%left '*' '/' '%'
%right tPLUSPLUS tMOINSMOINS UnaryStar
%left '(' '[' PostPlusPlus

%nonassoc tIF
%nonassoc tELSE

%%


Type :
	  tINT 			  {gDeclConst=0;} Indirections	{$$=$3;}
	| tCONSTANT tINT {gDeclConst=1;} Indirections	{$$=$4;}
	;

   
/*** Plus haut niveau: fonctions ou variables globales. ***/

ListeArguments :
	Type tID_VARIABLE {$$= argListe_new();argListe_add(&$$,$1);
                      tabSymboles_flagInit(declare($1, $2).addr);}
	| ListeArguments ',' Type tID_VARIABLE   {$$ = $1 ; argListe_add(&$$,$3); free($4);}
	;

OptListeArguments :
	ListeArguments {$$=$1;}
	| {$$= argListe_new();}
	;

Prototype :
	Type tID_VARIABLE '(' {tabSymboles_pushFrame(); gDeclGlobal=0;}
      OptListeArguments ')' {$$=tabFonction_add($2,$1,$5,gLineNo);}
	;
   
ProgrammeItem : 
	Declaration ';'
	| Prototype ';'      {tabSymboles_popFrame(); gDeclGlobal=1;}
	| Prototype {tabFonction_beginCorps($1,output_cur(),gLineNo);} 
		Block {gDeclGlobal=1; tabFonction_endCorps($1, output_cur()); tabSymboles_popFrame();}
	;

Programme : 
	ProgrammeItem Programme 
	|
	;

/*** gérer déclaration sous la forme "int a=1,b=2,c=3 ;" ***/
					
Indirections :
	  Indirections '*' 				{$$=type_pointeur($1, 0);}
   | Indirections '*' tCONSTANT	{$$=type_pointeur($1, 1);}
     /* "const int const v;" est une declaration légale */
   | tCONSTANT							{$$=type_entier(1);}
	|										{$$=type_entier(gDeclConst);}
	;

DeclarationInit: 
	  '[' tNUMBER ']' {declare_table(gDeclAddr.type, gDeclName, $2);}
   | '=' {gDeclAddr = declare(gDeclAddr.type, gDeclName);}		
		Expression		{type_setConst(&(gDeclAddr.type), 0); 
							 CE(affectation(gDeclAddr, $3, "="));}
   | 		{declare(gDeclAddr.type, gDeclName);}
   ;

DeclarationPart:
	Indirections tID_VARIABLE {gDeclAddr.type = $1; gDeclName = $2;} DeclarationInit;

DeclarationParts:
     DeclarationPart
   | DeclarationPart ',' DeclarationParts
   ;

DeclarationSimple :
	Type tID_VARIABLE {gDeclAddr.type = $1; gDeclName = $2;} 
	DeclarationInit
	;

Declaration :
	DeclarationSimple | DeclarationSimple ',' DeclarationParts;

/*** Contrôle de flux ***/
Statements : 
     Statement Statements
   | ;

Statement : 
	  Block
   | Declaration ';'
	| Expressions	';'					{CE($1);}
   | tPRINTF '(' Expression ')' ';'	{print($3);}
   | tRETURN Expression ';'			{CE($2);}
   | tBREAK ';'      {flowCtrl_addBreak(output_later());}
   | tCONTINUE ';'   {ControlFrame* f = flowCtrl_innerLoop();
                      if (!f) cerror("'continue' has no power here");
                      Addr a = {f->addr0 - output_cur(), LOCAL};
                      out(JMP, a);}
	| If_else
   | While
   | For
   | ';'
   ;
	

/* If: même état réutilisé dans les deux cas pour éviter une situation
 * indécidable de conflit. */
If_condition : tIF '(' Expressions ')'  {CE($3); flowCtrl_push(CTRL_IF);
                                        flowCtrl_top->addr1 = output_later();}
					Statement			  {flowCtrl_top->addr2 = output_later();
											   $$=$3.addr;} 
	;

If_else :
	  If_condition tELSE Statement		%prec tELSE
			{Addr a = {output_cur() - flowCtrl_top->addr2, LOCAL};
          out_at(flowCtrl_top->addr2, JMP, a);
          a.addr = flowCtrl_top->addr2+1 - flowCtrl_top->addr1;
			 out_at(flowCtrl_top->addr1, JMF, $1, a);
			 flowCtrl_pop(output_cur());}
  |
	  If_condition			%prec tIF
			{output_unput();
			 Addr a = {output_cur() - flowCtrl_top->addr1, LOCAL};
          out_at(flowCtrl_top->addr1, JMF, $1, a); 
  			 flowCtrl_pop(output_cur());}
  ;

While:
	tWHILE '(' 		{flowCtrl_push(CTRL_BOUCLE); flowCtrl_top->addr0 = output_cur();} 
	Expressions		{flowCtrl_top->addr1 = output_later(); CE($4);}
	')' Statement	{Addr a = {output_cur()+1 - flowCtrl_top->addr1, LOCAL};
                   out_at(flowCtrl_top->addr1, JMF, $4.addr, a);
                   a.addr = flowCtrl_top->addr0 - output_cur();
						 out(JMP, a);
						 flowCtrl_pop(output_cur());}
   ;

/* Optimisation: les segments sont réordonnés pour économiser deux JMP par itération */
For_Init:
     Expressions {CE($1);}
   |
   ;

For_Cond:
   Expressions {flowCtrl_top->addr1 = output_later();
                flowCtrl_top->arg = $1.addr;
                CE($1); $$ = 1;}
   |          {flowCtrl_top->addr1 = output_cur()-1; $$ = 0;}
   ;
   
For_Converg:
     /* Ce segment est le premier dans la boucle. Après Init, sauter après ce
      * segment pour la première itération. */
     Expressions {int cond = flowCtrl_top->addr0+1 + (output_cur() - (flowCtrl_top->addr1));
                  output_inverse(flowCtrl_top->addr0, 
                                 flowCtrl_top->addr1 +1,
                                 output_cur());
                  flowCtrl_top->addr1 = cond; // pos. of the JMF has changed
                  Addr a = {output_cur() - (flowCtrl_top->addr0 - 1), LOCAL};
                  out_at(flowCtrl_top->addr0 - 1, JMP, a);               
                  CE($1); $$ = 1;}
     /* Si pas de partie convergence: pas de JMP lors de l'entrée. */
   |        {output_inverse(flowCtrl_top->addr0 -1, 
                            flowCtrl_top->addr0,
                            output_cur());
             output_unput();
             flowCtrl_top->addr0--;
             flowCtrl_top->addr1--; // pos. of the JMF has changed
             $$ = 0;
            }
   ;
               
For:
	tFOR '('	
	For_Init ';'   {flowCtrl_push(CTRL_BOUCLE); 
                   flowCtrl_top->addr0 = output_later() + 1;}
	For_Cond ';' For_Converg ')'
	Statement	{Addr a = {output_cur()+1 - flowCtrl_top->addr1, LOCAL};
                if ($6)
                   out_at(flowCtrl_top->addr1, JMF, flowCtrl_top->arg, a);
                a.addr = flowCtrl_top->addr0 - output_cur();
                out(JMP, a);
                flowCtrl_pop(output_cur());}

Block:
	'{'            {tabSymboles_pushFrame();} 
	Statements '}'	{tabSymboles_popFrame();} 
	
	
/*** Expressions ***/

Expressions:
	  Expression						{$$ = $1;}
   | Expression ',' Expressions 	{$$ = $3;}
   ;

Expression : 
	  tNUMBER			{
                        $$.addr = tabSymboles_pushTmp();
                        out(AFC, $$.addr, $1);
                        $$.type = type_entier(1);
				 				$$.mode = ADDR_IMMEDIAT;
                     }
   | tID_VARIABLE    {$$.addr = use_var($1, &($$.type)); $$.mode = ADDR_IMMEDIAT;}
	| '&' tID_VARIABLE   %prec UnaryStar
                        {  $$.addr = tabSymboles_pushTmp();
	                        out(AFC, $$.addr, use_var($2, &($$.type)));
                           if ($$.addr.space == LOCAL) { // les pointeurs sont toujours absolus
                              Addr sp = {0, GLOBAL};
                              out(ADD, $$.addr, $$.addr, sp);
                           }
	                        $$.type = type_pointeur($$.type, 1);
									$$.mode = ADDR_IMMEDIAT;
	                     }
   | '*' Expression %prec UnaryStar     {$$ = followPtr($2);}
   | Expression '(' ')'                {cerror("Function calls are not supported yet");}
   | Expression '(' Expressions ')'    {cerror("Function calls are not supported yet");}
// priorités définies avec les tokens
   | '(' Expression ')'						{$$ = $2;}
   | Expression '=' Expression			{$$ = affectation($1, $3, "=");}
   | Expression '[' Expression ']'		{$$ = followPtr(binop(ADD, "[]", $1, $3));}
	| Expression '%' Expression			{$$ = binop(MOD, "%",  $1, $3);}
   | Expression '*' Expression			{$$ = binop(MUL, "*",  $1, $3);}
   | Expression '/' Expression			{$$ = binop(DIV, "/",  $1, $3);}
   | Expression '+' Expression			{$$ = binop(ADD, "+",  $1, $3);}
   | Expression '-' Expression			{$$ = binop(SOU, "-",  $1, $3);}
   | Expression '>' Expression			{$$ = binop(SUP, ">",  $1, $3);}
   | Expression '<' Expression	   	{$$ = binop(INF, "<",  $1, $3);}
	| Expression tPLUSEGAL Expression	{$$ = affectation($1, binop(ADD, "+=",  $1, $3), "+=");}
	| Expression tMOINSEGAL Expression	{$$ = affectation($1, binop(SOU, "-=",  $1, $3), "-=");}
	| Expression tMULEGAL Expression		{$$ = affectation($1, binop(MUL, "*=",  $1, $3), "*=");}
	| Expression tDIVEGAL Expression		{$$ = affectation($1, binop(DIV, "/=",  $1, $3), "/=");}
   | Expression tMODEGAL Expression		{$$ = affectation($1, binop(DIV, "%=",  $1, $3), "/=");}
	| tPLUSPLUS Expression 					{$$ = unop(ADD, 0, $2);}
	| Expression tPLUSPLUS     %prec PostPlusPlus   {$$ = unop(ADD, 1, $1);}
	| tMOINSMOINS Expression 				{$$ = unop(SOU, 0, $2);}
	| Expression tMOINSMOINS   %prec PostPlusPlus   {$$ = unop(SOU, 1, $1);}
   | Expression tGE Expression			{$$ = binop(GTE, ">=", $1, $3);}
   | Expression tLE Expression			{$$ = binop(LSE, "<=", $1, $3);}
   | Expression tEQUAL Expression	 	{$$ = binop(EQU, "==", $1, $3);}
   | Expression tNOT_EQUAL Expression	{$$ = binop(NEQ, "!=", $1, $3);}
   ;

%%

/******************************** Système **************************************/

char merror[256];
int non_fatal = 0;

void cerror(const char* s, ...)
{
	va_list a;
	va_start(a, s);
   strcpy(merror, "Error: ");
	vsnprintf(merror+7, 256-7, s, a);
	va_end(a);

	yyerror(merror);
}

void cwarning(const char* s, ...)
{
	va_list a;
	va_start(a, s);
   strcpy(merror, "Warning: ");
	vsnprintf(merror+9, 256-9, s, a);
	va_end(a);

   non_fatal = 1;
	yyerror(merror);
   non_fatal = 0;
}

void yyerror(char* s) 
{
   // filename, line number, and error message
   fprintf(stderr, "%s:%d> %s\n", gInputFileName, gLineNo, s);
   if (!non_fatal)
      exit(-1);
   // else: call by cerror, or by yacc.
}

int yywrap() // pas générée automatiquement par byacc
{
   // return 0 to keep parsing.
   // we can use this place to process several files in a single command line.
   return 1;
}

void usage()
{
	fprintf(stderr, "usage: compile infile.c -o outfile\n"
	                "Will generate 'outfile.asm' and 'outfile.bin'\n");
	exit(-1);
}

int main(int argc, const char** argv) 
{
	const char* outBaseName = NULL;
	
	// parse command line
	int i = 1;
	while (i < argc) 
	{
		if (argv[i][0] == '-') {
			if (strcmp(argv[i], "-o") == 0) {
				i++;
				if (i == argc) 
					usage();
				outBaseName = argv[i];
			}
			else
				usage();
		}
		else {
			gInputFileName = argv[i];
		}
		i++;
	}
	
	// open input file
	if (gInputFileName) {
		yyin = fopen(gInputFileName, "r");
		if (yyin) {
			printf("compiling %s\n", gInputFileName);
		}
		else {
			fprintf(stderr, "Cannot open file %s", gInputFileName);
			perror(":");
			exit(-1);
		}
	} 
	else {
		fprintf(stderr, "No input file was specified.\n");
		exit(0);
	}
	
	if (outBaseName) {
		if (output_open(outBaseName) < 0)
			exit(-1);
	}
	else 
		usage();
   
   
   output_later(); //for stack base initial value. see consolidate();
   Type t;
   tabSymboles_push(t, "#stackBase#", 1, 0); // reserve memory cell #0
   yyparse();
   consolidate();
   
	printf("Compilation succeeded\n");

	fclose(yyin);
	output_close();
	
   return 0;	
}
